import OpenGL
import OpenGL.GL
import OpenGL.GLU
import OpenGL.GLUT
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import math
from math import *
import Image
import random

looping=True

# Stolen from http://pyopengl.sourceforge.net/context/nehe6.html
def loadImage( imageName  ):
    """Load an image file as a 2D texture using PIL

    This method combines all of the functionality required to
    load the image with PIL, convert it to a format compatible
    with PyOpenGL, generate the texture ID, and store the image
    data under that texture ID.

    Note: only the ID is returned, no reference to the image object
    or the string data is stored in user space, the data is only
    present within the OpenGL engine after this call exits.
    """
    im = Image.open(imageName)
    try:
            # get image meta-data (dimensions) and data
            ix, iy, image = im.size[0], im.size[1], im.tostring("raw", "RGBA", 0, -1)
    except SystemError:
            # has no alpha channel, synthesize one, see the
            # texture module for more realistic handling
            ix, iy, image = im.size[0], im.size[1], im.tostring("raw", "RGBX", 0, -1)
    # generate a texture ID
    ID = glGenTextures(1)
    # make it current
    glBindTexture(GL_TEXTURE_2D, ID)
    glPixelStorei(GL_UNPACK_ALIGNMENT,1)
    # copy the texture into the current texture ID
    #glTexImage2D(GL_TEXTURE_2D, 0, 4, ix, iy, 0, GL_RGBA, GL_UNSIGNED_BYTE, image)
    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, ix, iy, GL_RGBA, GL_UNSIGNED_BYTE, image)
    
    # return the ID for use
    return ID



class Monster:
    x=y=z=.6
    vx=vy=vz=0
    yaw=0.0
    pitch=0.0
    
    vx=vy=vz=0
    goa=god=gow=gos=False

def processMonster(m,dt):
    if m.gow:
        m.vx=cos(m.yaw)*cos(m.pitch)*PLAYER_SPEED
        m.vy=sin(m.yaw)*cos(m.pitch)*PLAYER_SPEED
        m.vz=sin(m.pitch)*PLAYER_SPEED
    if keys.get('W'):
        m.vx=cos(m.yaw)*cos(m.pitch)*PLAYER_SPEED*9
        m.vy=sin(m.yaw)*cos(m.pitch)*PLAYER_SPEED*9
        m.vz=sin(m.pitch)*PLAYER_SPEED*9
    if m.gos:
        m.vx=-cos(m.yaw)*cos(m.pitch)*PLAYER_SPEED
        m.vy=-sin(m.yaw)*cos(m.pitch)*PLAYER_SPEED
        m.vz=-sin(m.pitch)*PLAYER_SPEED
        
    if m.goa:
        m.vx=-sin(m.yaw)*PLAYER_SPEED
        m.vy=+cos(m.yaw)*PLAYER_SPEED
        
    if m.god:
        m.vx=+sin(m.yaw)*PLAYER_SPEED
        m.vy=-cos(m.yaw)*PLAYER_SPEED
        

    m.x+=m.vx*dt
    m.y+=m.vy*dt
    m.z+=m.vz*dt
    m.vx=m.vy=m.vz=0
    

PLAYER_SPEED=0.000003
player=Monster()



textures={}
def getTextureId(name):
    if name in textures:
        return textures[name]
    textures[name]=loadImage(name)
    return textures[name]

def setTexture(name):
    i = getTextureId(name)
    glBindTexture(GL_TEXTURE_2D, i)


sprites=[]
class Sprite:
    x=y=z=10.0
    tex="cloud.png"
    def draw(self):
        setTexture(self.tex)
        
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
        if keys.get('1'):
          glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
          glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        else:
          glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
          glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glEnable(GL_ALPHA_TEST)
        glAlphaFunc(GL_GREATER,0.1)
        glPushMatrix()
        glTranslatef(self.x,self.y,self.z)
        glRotatef(90+player.yaw*180/(atan(1)*4),0,0,1)
        glBegin(GL_QUADS)
        
        glColor3f(1,1,1,1)
        glNormal3f(0,0,1)
        
        h=1
        w=0.5
        glTexCoord2f(0,0)
        glVertex3f(-w,0,0)
        
        glTexCoord2f(0,1)
        glVertex3f(-w,0,1)
        
        glTexCoord2f(-1,1)
        glVertex3f(w,0,1)
        
        glTexCoord2f(-1,0)
        glVertex3f(w,0,0)
        glEnd()
        glPopMatrix()
        glDisable(GL_BLEND)

sprites += [Sprite()]


keys={}
def kbd(k,b,c):
    global looping
    if k=='q':
        looping=False
    if k=='w':
        player.gow=True
    if k=='s':
        player.gos=True
    if k=='a':
        player.goa=True
    if k=='d':
        player.god=True
    keys[k]=True
 
def kbdup(k,b,c):
    if k=='w':
        player.gow=False
    if k=='s':
        player.gos=False
    if k=='a':
        player.goa=False
    if k=='d':
        player.god=False
    keys[k]=False


prevx=0
prevy=0
def mouse(x,y):
    global prevx,prevy
    dx=prevx-x
    dy=prevy-y
    player.yaw+=dx*0.01
    player.pitch+=dy*0.01
    if player.pitch>1.57: player.pitch=1.57
    if player.pitch<-1.57: player.pitch=-1.57
    prevx=x
    prevy=y
    pass

def drawAxes():
    glBegin(GL_LINES)
    glColor3f(1,0,0)
    glVertex3f(0,0,0)
    glVertex3f(1,0,0)
    glColor3f(0,1,0)
    glVertex3f(0,0,0)
    glVertex3f(0,1,0)
    glColor3f(0,0,1)
    glVertex3f(0,0,0)
    glVertex3f(0,0,1)
    glEnd()

def setupLight():
    glEnable(GL_LIGHTING)
    glEnable(GL_LIGHT0)
    glLightfv(GL_LIGHT0,GL_DIFFUSE,(1,1,1,1))
    glLightfv(GL_LIGHT0,GL_AMBIENT,(0,.3,.5,1))
    glLightfv(GL_LIGHT0,GL_POSITION,(100,100,100,0))
    glEnable(GL_LIGHT1)
    glLightfv(GL_LIGHT1,GL_DIFFUSE,(0.0,0.0,0.0,1))
    glLightfv(GL_LIGHT1,GL_AMBIENT,(0,.1,.1,1))
    glLightfv(GL_LIGHT1,GL_POSITION,(10,20,30,0))

surfaceId=0
def drawSurface():
    global surfaceId
    dx=1.0
    dy=1.0
    def F(x,y):
        return sin(0.3*x)+sin(0.7*y)*sin(0.5*y)
    def CF(x,y):
        return (2+F(x,y))*0.25
    def CN(x,y):
        dzdx=F(x+1,y)-F(x,y)
        dzdy=F(x,y+1)-F(x,y)
        (x,y,z)=(-dzdx,-dzdy,1)
        ab=(x*x+y*y+z*z)**0.5
        return (x/ab,y/ab,z/ab)
    if surfaceId:
        glCallList(surfaceId)
    else:
        surfaceId=glGenLists(1)
        glNewList(surfaceId,GL_COMPILE_AND_EXECUTE)
        glBegin(GL_QUADS)
        glColor3f(.4,.8,.3)
        for a in xrange(20):
            for b in xrange(20):
                aa=a+1
                bb=b+1
                x=a*dx
                y=b*dy

                xx=aa*dx
                yy=bb*dy

                glColor3f(.4,CF(x,y),.3)
                glNormal3fv(CN(x,y))
                glTexCoord2f(x,y)
                glVertex3f(x,y,F(x,y))
                
                glColor3f(.4,CF(xx,y),.3)
                glNormal3fv(CN(xx,y))
                glTexCoord2f(xx,y)
                glVertex3f(xx,y,F(xx,y))

                glColor3f(.4,CF(xx,yy),.3)
                glNormal3fv(CN(xx,yy))
                glTexCoord2f(xx,yy)
                glVertex3f(xx,yy,F(xx,yy))

                glColor3f(.4,CF(x,yy),.3)
                glNormal3fv(CN(x,yy))
                glTexCoord2f(x,yy)
                glVertex3f(x,yy,F(x,yy))
                
        glEnd()
        glEndList()


floorId=0
def drawFloor():
    global floorId
    dx=1.0
    dy=1.0
    def F(x,y):
        return 3
    def CN(x,y):
        dzdx=F(x+1,y)-F(x,y)
        dzdy=F(x,y+1)-F(x,y)
        (x,y,z)=(-dzdx,-dzdy,1)
        ab=(x*x+y*y+z*z)**0.5
        return (x/ab,y/ab,z/ab)
    glDisable(GL_TEXTURE_2D)
    if floorId:
        glCallList(floorId)
    else:
        surfaceId=glGenLists(1)
        glNewList(surfaceId,GL_COMPILE_AND_EXECUTE)
        glBegin(GL_QUADS)
        glColor3f(.4,.8,.3)
        for a in xrange(20):
            for b in xrange(20):
                aa=a+1
                bb=b+1
                x=a*dx
                y=b*dy

                xx=aa*dx
                yy=bb*dy

                glColor3f(.7,.7,.7)
                glNormal3f(0,0,1)
                glVertex3f(x,y,F(x,y))
                
                glVertex3f(xx,y,F(xx,y))

                glVertex3f(xx,yy,F(xx,yy))

                glVertex3f(x,yy,F(x,yy))
                
        glEnd()
        glEndList()

    glEnable(GL_TEXTURE_2D)

def makeSphere():
  lst=[]
  def Coord(a,b):
    aa=a/8.0*5
    bb=b/8.0*3-1.5
    return ( cos(aa)*cos(bb)*1.0,
             sin(aa)*cos(bb)*1.0,
             sin(bb)*1.0)
  for a in xrange(8):
    for b in xrange(8):
      lst+=[Coord(a,b)]
      lst+=[Coord(a+1,b)]
      lst+=[Coord(a+1,b+1)]
      
      lst+=[Coord(a,b)]
      lst+=[Coord(a+1,b+1)]
      lst+=[Coord(a,b+1)]
  #print lst
  return lst




  

inited=False
tex1=0
tex2=0
def initOnce():
    global inited
    global etime
    global sprites
    if inited: return
    inited=True
    global tex1,tex2
    for i in xrange(10):
      s=Sprite()
      s.x=random.random()*10
      s.y=random.random()*10
      s.z=random.random()*10
      s.tex='cloud.png'
      sprites += [s]
      
    for i in xrange(10):
      s=Sprite()
      s.x=random.random()*3+5
      s.y=random.random()*3+5
      def F(x,y):
          return sin(0.3*x)+sin(0.7*y)*sin(0.5*y)
      s.z=F(s.x,s.y)
      s.tex='tree.png'
      sprites += [s]
    

etime=0
spn=0
def display():
    initOnce()
    global etime
    ct=glutGet(GLUT_ELAPSED_TIME)
    dt=ct-etime
    ct=etime

    processMonster(player,dt)
    
    glClearColor(.4,.5,.6,1.0)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT )
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(90,glutGet(GLUT_WINDOW_WIDTH)*1.0/glutGet(GLUT_WINDOW_HEIGHT),0.1,100.0)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    gluLookAt(player.x,player.y,player.z,
              player.x+cos(player.yaw)*cos(player.pitch),
              player.y+sin(player.yaw)*cos(player.pitch),
              player.z+sin(player.pitch),
              0,0,1)
    glEnable(GL_DEPTH_TEST)
    glEnable(GL_CULL_FACE)
    glEnable(GL_TEXTURE_2D)

    
    #glBindTexture(GL_TEXTURE_2D, tex1)
    setupLight()
    drawAxes()
    
    setTexture('grass.png')
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
    
    drawSurface()
    
    sp=makeSphere()
    # Draw sphere
    glPushMatrix()
    glTranslatef(2,3,2)
    glBegin(GL_TRIANGLES)
    for x,y,z in sp:
      glNormal3f(x,y,z)
      glVertex3f(x,y,z)
    glEnd()
    glPopMatrix()
    
    
    glPushMatrix()
    glTranslatef(2,3,2)
    # Draw Sphere shadow volume
    edges=[]
    i=0
    # Extract edges
    while i+3<len(sp):
      if sp[i][2]>0:
         i+=3
         continue
      edges+=[sorted([sp[i+0], sp[i+1]], key=lambda x: x[0]+x[1]+x[2])]
      edges+=[sorted([sp[i+1], sp[i+2]], key=lambda x: x[0]+x[1]+x[2])]
      edges+=[sorted([sp[i+2], sp[i+0]], key=lambda x: x[0]+x[1]+x[2])]
      i+=3
    edges.sort(key=lambda e: e[0][0]+
                          e[0][1]*10+
                          e[0][2]*100+
                          e[1][0]*1000+
                          e[1][1]*10000+
                          e[1][2]*100000)
    uniq_edges=[edges[0]]
    last_edge=edges[0]
    pu=0
    for edge in edges:
      if (edge[0][0]==last_edge[0][0] and
          edge[0][1]==last_edge[0][1] and
          edge[0][2]==last_edge[0][2] and
          edge[1][0]==last_edge[1][0] and
          edge[1][1]==last_edge[1][1] and
          edge[1][2]==last_edge[1][2]):
        pass
      else:
        last_edge=edge
        uniq_edges+=[edge]
    # extrude edges in some direction
    d=(-3,-4,-5)
    glEnable(GL_STENCIL_TEST)
    
    glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE);
    glDepthMask(GL_FALSE);
    glStencilFunc(GL_ALWAYS, 1, 1)
    glStencilOp(GL_KEEP,GL_KEEP,GL_INVERT); # Write only if depth test pass
    glDisable(GL_CULL_FACE)
    glBegin(GL_QUADS)
    for edge in edges:
        glVertex3f(edge[0][0],   edge[0][1],   edge[0][2])
        glVertex3f(edge[0][0]-30,edge[0][1]-40,edge[0][2]-50)
        glVertex3f(edge[1][0]-30,edge[1][1]-40,edge[1][2]-50)
        glVertex3f(edge[1][0],   edge[1][1],   edge[1][2])
    glEnd()
    glPopMatrix()
    glDisable(GL_STENCIL_TEST)
    glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
    glDepthMask(GL_TRUE);
    
    sprites.sort(key=lambda s:- ((player.x-s.x)**2+(player.y-s.y)**2+(player.z-s.z)**2))
    for s in sprites:
        s.draw()
    
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    glDisable(GL_DEPTH_TEST)
    glDisable(GL_ALPHA_TEST)
    glDisable(GL_LIGHTING)
    glEnable(GL_STENCIL_TEST)
    glStencilFunc(GL_EQUAL, 1, 1)
    glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP)
    
    glBegin(GL_QUADS)
    glColor4f(1,0,0,1)
    glVertex3f(-1,-1,0)
    glVertex3f( 1,-1,0)
    glVertex3f( 1, 1,0)
    glVertex3f(-1, 1,0)
    glEnd()
    glDisable(GL_STENCIL_TEST)
        
    
    glutSwapBuffers()

def idle():
    display()

def main():
    glutInit()
    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL)
    glutInitWindowSize(800,600)
    glutCreateWindow('qwe')
    
    glutDisplayFunc(display)
    glutKeyboardFunc(kbd)
    glutKeyboardUpFunc(kbdup)
    glutPassiveMotionFunc(mouse)
    while(looping):
        glutMainLoopEvent()
        glutPostRedisplay()
    pass


main()
